<?php
declare(strict_types=1);
/**
 * @Purpose      :  通用函数库
 * @Filename     :  Misc.Func.php
 * @Date         :  2019-03-20
 * @Time         :  15:00
 * @Version      :  1.0
 * @Function     :  通用函数库
 * @Description  :  通用函数库
 * @History      :  1.0
 */

use Swoole\Coroutine;
use Swoole\Coroutine\System;

function LowercaseToHump($string, $capitalizeFirstCharacter = true) {
    $str = str_replace('_', '', ucwords($string, '_'));
    if (!$capitalizeFirstCharacter) {
        $str = lcfirst($str);
    }
    return $str;
}

function HumpToLowercase($str) {
    $underscored = preg_replace('/(?<!^)([A-Z])/', '_$1', $str);
    // 将结果转换为小写
    return  strtolower(ltrim($underscored,'_'));
}

/**
 * 生成签名
 */
function makeSign(array $data,string $start = 's') : string
{
    $data['timestamp'] = time();
    $key = strtoupper(md5("E" . $data['timestamp'] . "S"));
    ksort($data);
    $s = '';
    $url = "";
    foreach ($data as $k => $v) {
        $s = $s . $k . $v;
        $url .= "&" . $k . "=" . $v;
    }
    return $start . strtoupper(md5($key . $s . $key)) . base64_encode($url);
}
/**
 * 验证签名
 */
function checkSign(array $data,string $sign):bool
{
    empty($data['timestamp']) && $data['timestamp'] = "78378766";
    $key = strtoupper(md5("S" . $data['timestamp'] . "E"));
    ksort($data);
    $s = '';
    foreach ($data as $k => $v) {
        $s .= $k . $v;
    }
    return $sign == strtoupper(md5($key . $s . $key));
}


function tox16(string $num, $length = 2):string
{
    return strtoupper(str_pad(base_convert($num, 10, 16), $length, "0", STR_PAD_LEFT));
}

function tox10(string $data): string
{
    return base_convert($data, 16, 10);
}


/***
 * 根据条件筛选数据
 * 返回：满足筛选条件的数据集合（数组）
 * 数组：$arr: [{id: 1, name: 'a'}, {id: 2, name: 'b'}, {id: 3, name: 'c'}];
 * 使用：
 * find_data_row($arr, array('id'=> 1, 'name'=>'a'));
 */
function find_data_row(array $arr_data, array $arr_key_value):array
{
    return array_filter($arr_data, function ($obj) use ($arr_key_value) {
        foreach ($arr_key_value as $key => $value) {
            // 注意：筛选条件中的类型和值 要和数据源中的完全相等
            // 如有必要，也可以将这里的 !== 改为 !=。
            if (is_array($value)) {
                if (in_array($obj[$key], $value)) {
                    return true;
                }
            } elseif ($obj[$key] == $value) {
                return true;
            }
        }
        return false;
    }, ARRAY_FILTER_USE_BOTH);
}



function ColorToRgb(string $color):array
{
    $color = ltrim($color, '#');
    $rgb = str_split($color, 2);
    $rgb[0] = tox10($rgb[0]);
    $rgb[1] = tox10($rgb[1]);
    $rgb[2] = tox10($rgb[2]);
    return $rgb;
}




/**
 * @title IP转换
 */
function Ip2Region(string $ip):?array
{
    if(empty($_ENV['Ip2Region'])){
        return [];
    }
    return $_ENV['Ip2Region']->memorySearch($ip);
}


/**
 * 获取协程ID
 */
function getcid():int
{
    return Coroutine::getcid();
}

function removeBOM(string $str):string
{
    if (strlen($str) >= 3) {
        $c0 = ord($str[0]);
        $c1 = ord($str[1]);
        $c2 = ord($str[2]);

        if ($c0 == 0xFE && $c1 == 0xFF) {
            // -- UTF-16BE BOM文件头: [0xFE, 0xFF],
            $str = substr($str, 2);
        } else if ($c0 == 0xFF && $c1 == 0xFE) {
            // -- UTF-16LE BOM文件头: [0xFF, 0xFE],
            $str = substr($str, 2);
        } else if ($c0 == 0xEF && $c1 == 0xBB && $c2 == 0xBF) {
            // -- UTF-8 BOM文件头: [0xEF, 0xBB, 0xBF]
            $str = substr($str, 3);
        }
    }

    return $str;
}

function EchoLog(string|array $string = null,string $type = null):void
{
    $str="";
    if (is_array($string)) {
        $string = var_export($string, true) . PHP_EOL;
    }
    switch ($type) {
        case 's':
            $str = "\033[1;36m[" . date('Y-m-d H:i:s') . " SUCCESS]\033[0m \033[3;37m" . $string;
            break;
        case 'w':
            $str = "\033[1;33m[" . date('Y-m-d H:i:s') . " WARNING]\033[0m \033[3;37m" . $string;
            break;
        case 'e':
            $str = "\033[5;31m[" . date('Y-m-d H:i:s') . " ERROR  ]\033[0m \033[3;37m" . $string;
            break;
        case 'i':
            $str = "\033[1;32m[" . date('Y-m-d H:i:s') . " INFO   ]\033[0m \033[3;37m" . $string;
            break;
        case 'rf':
            echo "\033[s\033[3;37m" . $string . "\033[0m\033[u";
             break;
        default:
            $str = "\033[3;37m" . $string;
            break;
    }

    if (_CONF('debug')) {
        echo $str . "\033[0m" . PHP_EOL;
    }
}

function encrypt(string $key,string $str):string
{
    $iv = substr($key, 0, 16);
    return xn_urlencode(base64_encode(openssl_encrypt($str, "aes-256-cbc", $key, OPENSSL_RAW_DATA, $iv)));
}

function decrypt(string $key,string $str):string
{
    $iv = substr($key, 0, 16);
    return trim(openssl_decrypt(base64_decode(xn_urldecode($str)), "aes-256-cbc", $key, OPENSSL_RAW_DATA, $iv));
}


/**
 * 驼峰转下划线规则
 *
 * @param string $node 节点名称
 *
 * @return string
 */
function parseString(string $node):string
{
    $nodes = explode('/', $node);
    if (count($nodes) > 1) {
        $dots = [];
        foreach (explode('.', $nodes[1]) as $dot) {
            $dots[] = trim(preg_replace("/[A-Z]/", "_\\0", $dot), "_");
        }
        $nodes[1] = join('.', $dots);
    }
    return strtolower(join('/', $nodes));
}

/**
 * 获取所有PHP文件
 *
 * @param string $dirname 扫描目录
 * @param array $data 额外数据
 * @param string $ext 有文件后缀
 *
 * @return array
 */
function scanPath(string $dirname,array $data = [],string $ext = 'php'):array
{
    foreach (glob("$dirname*") as $file) {
        if (is_dir($file)) {
            $data = array_merge($data, scanPath("$file/", [], $ext));
        } else if (is_file($file) && pathinfo($file, PATHINFO_EXTENSION) === $ext) {
            $data[] = str_replace('\\', '/', $file);
        }
    }
    return $data;
}

function formatTimeElapsed(int $seconds):string
{
    $years = floor($seconds / (365*24*60*60));
    $seconds %= (365*24*60*60);

    $months = floor($seconds / (30*24*60*60));
    $seconds %= (30*24*60*60);

    $days = floor($seconds / (24*60*60));
    $seconds %= (24*60*60);

    $hours = floor($seconds / 3600);
    $seconds %= 3600;

    $minutes = floor($seconds / 60);
    $seconds %= 60;
    return sprintf('%d年%d月%d日 %02d:%02d:%02d', $years, $months, $days, $hours, $minutes, $seconds);
}
function humandate(int $timestamp,array $lan = []):string
{

    $time = time();
    $seconds = $time - $timestamp;
    empty($lan) and $lan = [
        'month_ago' => '月前',
        'day_ago' => '天前',
        'hour_ago' => '小时前',
        'minute_ago' => '分钟前',
        'second_ago' => '秒前',
    ];

    if ($seconds > 31536000) {
        return date('Y-n-j', $timestamp);
    } else if ($seconds > 2592000) {
        return floor($seconds / 2592000) . $lan['month_ago'];
    } else if ($seconds > 86400) {
        return floor($seconds / 86400) . $lan['day_ago'];
    } else if ($seconds > 3600) {
        return floor($seconds / 3600) . $lan['hour_ago'];
    } else if ($seconds > 60) {
        return floor($seconds / 60) . $lan['minute_ago'];
    } else {
        return $seconds . $lan['second_ago'];
    }
}


function humannumber(int $num):string
{
    $num > 100000 && $num = ceil($num / 10000) . '万';
    return $num;
}

function humansize(int $num):string
{
    if ($num > 1099511627776) {
        return number_format($num / 1099511627776, 2, '.', '') . 'T';
    } else if ($num > 1073741824) {
        return number_format($num / 1073741824, 2, '.', '') . 'G';
    } else if ($num > 1048576) {
        return number_format($num / 1048576, 2, '.', '') . 'M';
    } else if ($num > 1024) {
        return number_format($num / 1024, 2, '.', '') . 'K';
    } else {
        return $num . 'B';
    }
}

function sys_getrusage()
{
    $dat = getrusage();
    echo $dat["ru_oublock"]; // 块输出操作数
    echo $dat["ru_inblock"]; // 块输入操作数
    echo $dat["ru_msgsnd"]; // 发送的 IPC 消息数
    echo $dat["ru_msgrcv"]; // 接收的 IPC 消息数
    echo $dat["ru_maxrss"]; // maximum resident set size
    echo $dat["ru_ixrss"]; // 整数类型的共享内存大小
    echo $dat["ru_idrss"]; // 整数类型的非共享内存大小
    echo $dat["ru_minflt"]; // 页面回收次数（软分页错误）
    echo $dat["ru_majflt"]; // 页面错误次数（硬分页错误）
    echo $dat["ru_nsignals"]; // 接收到的信号数
    echo $dat["ru_nvcsw"]; // number of voluntary context switches
    echo $dat["ru_nivcsw"]; // number of involuntary context switches
    echo $dat["ru_nswap"]; // 交换次数
    echo $dat["ru_utime.tv_usec"]; // 用户使用时间（微秒）
    echo $dat["ru_utime.tv_sec"]; // 用户使用时间（秒）
    echo $dat["ru_stime.tv_usec"]; // 系统使用时间（微秒）
    echo $dat["ru_stime.tv_sec"]; // 系统使用时间（秒）
}

function sys_cpu(): array
{
    try {
        $ret = System::exec("cat /proc/cpuinfo");
        $output=trim($ret['output']);
        $data = explode(PHP_EOL, $output);
        $array = [];
        foreach ($data as $row) {
            $row=trim($row);
            if(!empty($row)){
                $ret = explode(":", $row);
                $array[] =[
                    'k'=>trim(str_replace(' ','',$ret[0])),
                    'v'=>trim($ret[1]),
                ];
            }
        }
        return  array_values(arrlist_change_key($array,'k'));
    }catch (\Throwable $err){
       // var_dump($err);
    }
    return [];
}

function sys_mem(): array
{
    try {
        $ret = System::exec("cat /proc/meminfo");
        $output=trim($ret['output']);
        $data = explode(PHP_EOL, $output);
        $array = [];
        foreach ($data as $row) {
            $row=trim($row);
            if(!empty($row)){
                $ret = explode(":", $row);
                $v = explode(" ", trim($ret[1]));
                $array[] =[
                    'k'=>trim(str_replace(' ','',$ret[0])),
                    'v'=>humansize((int)bcmul($v[0],'1024',0)),
                ];
            }
        }
        return  array_values(arrlist_change_key($array,'k'));
    }catch (\Throwable $err){
        //var_dump($err);
    }
    return [];
}

/*
function sys_fmt(): array
{
    global $server_worker;
    $temp = [];
    $out=[];
    if(!str_starts_with(PHP_OS, "CYGWIN")){

        $ret = System::exec("ps aux");
        $output = $ret['output'];
        $array = [
            'USER' => '用户', 'PID' => '进程ID', 'PPID' => '父进程ID', '%CPU' => 'CPU', '%MEM' => '内存', 'VSZ' => '虚拟内存', 'RSS' => '实际内存', 'TT' => '终端', 'TTY' => '终端', 'STAT' => '状态', 'START' => '启动时间', 'STARTED' => '启动时间', 'TIME' => '总CPU时间', 'COMMAND' => '正在执行的命令行命令'
        ];
        //USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
        $STAT = [
            'D' => '休眠',
            'R' => '运行',
            'S' => '休眠',
            'T' => '停止',
            'W' => '休眠',
            'X' => '死掉',
            'Z' => '僵尸',
        ];

        for ($i = 0; $i < 10; $i++) {
            $output = str_replace('  ', ' ', $output);
        }

        $data = explode(PHP_EOL, $output);

        $tmp1 = explode(" ", array_shift($data));

        $k1 = 0;
        $k2 = 0;
        $k3 = 0;
        foreach ($tmp1 as $k => $v) {
            if ($v == 'STAT') {
                $k1 = $k;
            } elseif ($v == 'VSZ') {
                $k2 = $k;
            } elseif ($v == 'RSS') {
                $k3 = $k;
            }
            $temp[$k] = ['k' => str_replace('%', '', $v), 'name' => $array[$v]];
        }
        $length = count($temp);


        $ret = System::exec("ps aux | grep " . $server_worker->name);
        $output = $ret['output'];
        for ($i = 0; $i < 10; $i++) {
            $output = str_replace('  ', ' ', $output);
        }
        $data = explode(PHP_EOL, $output);
        foreach ($data as $row) {
            if (str_replace('grep', '', $row) != $row) {
                continue;
            }
            $row = explode(" ", $row);
            $n = count($row);

            if ($n >= $length) {
                $t = $row[$k1][0];
                $row[$k1] = $STAT[$t] ?? '未知';
                if ($n > $length) {
                    for ($i = 0; $i < $n - $length; $i++) {
                        $row[$length - 1] .= ' ' . $row[$length + $i];
                        unset($row[$length + $i]);
                    }
                }

                $line = [];
                foreach ($temp as $k => $v) {
                    $line[$v['k']] = $row[$k];
                }
                $out[] = $line;
            }
        }
    }
    return ['title' => $temp, 'data' => $out];
}
*/
/**
 * @Purpose      :  利用递归的方式统计目录的大小
 */
function dirSize(string $dirName):string
{
    $dir_size = 0;
    try {
        //if(!str_starts_with(PHP_OS, "CYGWIN")){
            $ret = System::exec("du -sh " . $dirName);
            $dir_size = explode("\t", $ret['output'])[0];
            return trim($dir_size);
        //}
        /*
        $dir = opendir($dirName);
        while ($fileName = readdir($dir)) {
            $file = $dirName . "/" . $fileName;
            if ($fileName != "." && $fileName != "..") {      // 一定要进行判断，否则会出现错误的
                if (is_dir($file)) {
                    $dir_size = bcadd($dir_size, dirSize($file));    // 这个地方必须是 $dirsize += 是若目录，再次递归的时候，$dirsize 又被重新置 0 了
                } else {
                    $dir_size = bcadd($dir_size, (string)filesize($file));
                }
            }
        }
        closedir($dir);
        */
    }catch (Throwable){
        //var_dump($err);
    }

    return humansize($dir_size);
}

/**
 * 此处为IDE自动提示准备，故而始终加载旧文件
 */
function IDE_include(string $src_file,string $old = ''): string
{
    if ($old) {
        static $len;
        $len = $len ?: strlen(dirname(__ROTDIR__));
        $tmpfile = __IDEDIR__ . substr(str_replace('/', '_', $src_file), $len);
        $s = file_get_contents_try($src_file);
        if (empty($s)) return $src_file;
        $s = preg_replace('#//IDE_LOAD_START.+//IDE_LOAD_END#is', "\n" . $old . "\n", $s);
        $s = preg_replace_callback('#//\s*replace\s+(\S+)#is', 'plugin_compile_replace_callback', $s);
        !empty($s) and file_put_contents_try($tmpfile, $s);
    }
    var_dump($src_file);
    return $src_file;
}

function load_file($m) :string
{
    $file_name = __TMPDIR__ .'code/'. $m['1'] . '.php';
    $s = '';
    if (is_file($file_name)) {
        $s = file_read($file_name);
        $pattern = '/^<\?php\s*exit\(\);/';
        $s = preg_replace($pattern, '', $s, 1);
    }
    return $s;
}

function file_name($path):string
{
    return substr($path, strrpos($path, '/') + 1);
}

// 将变量写入到文件，根据后缀判断文件格式，先备份，再写入，写入失败，还原备份
function file_replace_var($filepath, $replace = []):bool|int
{
    $ext = file_ext($filepath);
    if ($ext == 'php') {
        $arr = include $filepath;
        $arr = array_merge($arr, $replace);
        $s = "<?php\r\nreturn " . var_export($arr, true) . ";\r\n?>";
        // 备份文件
        file_backup($filepath);
        $r = file_put_contents_try($filepath, $s);
        $r != strlen($s) ? file_backup_restore($filepath) : file_backup_unlink($filepath);
        return $r;
    } else if ($ext == 'js' || $ext == 'json') {
        $s = file_get_contents_try($filepath);
        $arr = xn_json_decode($s);
        if (empty($arr)) return FALSE;
        $arr = array_merge($arr, $replace);
        $s = xn_json_encode($arr);
        file_backup($filepath);
        $r = file_put_contents_try($filepath, $s);
        $r != strlen($s) ? file_backup_restore($filepath) : file_backup_unlink($filepath);
        return $r;
    }
    return FALSE;
}
function file_save_var($data,$filename):bool|int
{
    $ext = file_ext($filename);
    if ($ext == 'php') {
        $s = "<?php\r\nreturn " . var_export($data, true) . ";\r\n?>";
        // 备份文件
        file_backup($filename);
        $r = file_put_contents_try($filename, $s);
        $r != strlen($s) ? file_backup_restore($filename) : file_backup_unlink($filename);
        return $r;
    }
    return FALSE;
}

function file_backname($filepath): string
{
    //$dirname = dirname($filepath);
    //$filename = file_name($filepath);
    $filepre = file_pre($filepath);
    $fileext = file_ext($filepath);
    return "$filepre.backup.$fileext";
}

// 文件的前缀，不包含最后一个 .
function file_pre($filename): string
{
    return substr($filename, 0, strrpos($filename, '.'));
}

function is_backfile($filepath): bool
{
    return str_contains($filepath, '.backup.');
}

// 备份文件
function file_backup($filepath): bool
{
    $back_file = file_backname($filepath);
    if (is_file($back_file)) return TRUE; // 备份已经存在
    $r = xn_copy($filepath, $back_file);
    clearstatcache();
    return $r && filesize($back_file) == filesize($filepath);
}

// 还原备份
function file_backup_restore($filepath): bool
{
    $backfile = file_backname($filepath);
    $r = xn_copy($backfile, $filepath);
    clearstatcache();
    $r && filesize($backfile) == filesize($filepath) && xn_unlink($backfile);
    return $r;
}

// 删除备份
function file_backup_unlink($filepath): bool
{
    $backfile = file_backname($filepath);
    return xn_unlink($backfile);
}

function file_ext($filename, $max = 16): array|string
{
    $ext = strtolower(substr(strrchr($filename, '.'), 1));
    $ext = xn_urlencode($ext);
    strlen($ext) > $max and $ext = substr($ext, 0, $max);
    if (!preg_match('#^\w+$#', $ext)) $ext = 'attach';
    return $ext;
}

function formatSize( $num): string
{

    if ($num > 1073741824) {
        return number_format($num / 1073741824, 2, '.', '') . 'G';
    } elseif ($num > 1048576) {
        return number_format($num / 1048576, 2, '.', '') . 'M';
    } elseif ($num > 1024) {
        return number_format($num / 1024, 2, '.', '') . 'K';
    } else {
        return $num . 'B';
    }
//
//    if ($b > 1000) {
//        $temp = $b / 1000;
//        return formatSize($temp, $times + 1);
//    } else {
//        $unit = 'B';
//        switch ($times) {
//            case '0':
//                $unit = 'B';
//                break;
//            case '1':
//                $unit = 'KB';
//                break;
//            case '2':
//                $unit = 'MB';
//                break;
//            case '3':
//                $unit = 'GB';
//                break;
//            case '4':
//                $unit = 'TB';
//                break;
//            case '5':
//                $unit = 'PB';
//                break;
//            case '6':
//                $unit = 'EB';
//                break;
//            case '7':
//                $unit = 'ZB';
//                break;
//            default:
//                $unit = '单位未知';
//        }
//        return sprintf('%.2f', $b) . $unit;
//    }
}

function file_get_contents_try(string $file,int $times = 3): false|string
{
    while ($times-- > 0) {
        $fp = fopen($file, 'rb');
        if ($fp) {
            $size = filesize($file);
            if ($size == 0) return '';
            $s = fread($fp, $size);
            fclose($fp);
            return $s;
        } else {
            sleep(1);
        }
    }
    return FALSE;
}

function file_put_contents_try(string $file,string $s,int $times = 30): false|int
{
    while ($times-- > 0) {
        $fp = fopen($file, 'wb');
        if ($fp and flock($fp, LOCK_EX)) {
            $n = fwrite($fp, $s);
            flock($fp, LOCK_UN);
            fclose($fp);
            clearstatcache();
            return $n;
        } else {
            usleep(100);
        }
    }
    return false;
}

function get_file_methods($file): array
{
    $s = php_strip_whitespace($file);
    if (empty($s)) return [];
    preg_match_all("#function(.+)\(#isU", $s, $arr);
    $list = [];
    foreach ($arr[1] as $v) {
        $list[] = trim($v);
    }
    return $list;
}

function convertUnderline($str): array|string
{
    $str = ucwords(str_replace('-', ' ', strtolower($str)));
    return str_replace(' ', '', $str);
    //return $ucfirst ? ucfirst($str) : $str;
}

function humpToLine($str): string
{
    $str = preg_replace_callback('/([A-Z])/', function ($matches) {
        return '-' . strtolower($matches[0]);
    }, $str);
    return ltrim($str, '-');
}

/**
 * 输出xml字符
 * @throws Exception
 **/
function ArrayToXml($input): string
{
    $xml = '<xml>';
    foreach ($input as $key => $val) {
        $xml .= '<' . $key . '>' . $val . '</' . $key . '>';
    }
    $xml .= '</xml>';
    return $xml;
}

function xn_log($s, $file = 'error'): void
{
    if ($s) {
        $day = date('Ym');
        $logpath = __LOGDIR__ . $day . '/';
        if (!is_dir($logpath)) {
            try {
                mkdir($logpath, 0777, true);
                chmod($logpath, 0777);
            }catch (Exception $err){
                var_dump("xn_log mkdir error ". __STADIR__ . $day);
            }


        }
        error_log("***" . date('His') . "***\r\n" . $s . "\r\n", 3, $logpath . date('d') . '_' . $file . '.log');
    }
}

function stat_log($s, $file = 'stat'): void
{
    if ($s) {
        $day = date('Ym');
        $logpath = __STADIR__ . $day . '/' . date("d") . '_' . $file . '.log';
        if (!is_dir(__STADIR__ . $day)) {
            try {
                mkdir(__STADIR__ . $day, 0777, true);
                chmod(__STADIR__ . $day, 0777);
            }catch (Exception $err){
                var_dump("stat_log mkdir error ". __STADIR__ . $day);
            }
        }
        //System::writeFile($logpath , date('H:i') . "\t" . implode("\t", $s) . "\r\n", FILE_APPEND);
        error_log(date('H:i') . "\t" . implode("\t", $s) . "\r\n", 3, $logpath);
    }
}

function read_stat_log($day = '', $file = 'stat'): false|string|null
{

    $logpath = __STADIR__ . mb_substr($day, 0, 6) . '/' . mb_substr($day, 6, 2) . '_' . $file . '.log';
    return is_file($logpath) ? file_read($logpath) : false;
}

function read_stat_log_day($file = 'stat'): array
{
    $str = [];
    $include_model_files = glob(__STADIR__ . '/*/*' . $file . '.log');
    foreach ($include_model_files as $model_files) {
        $str[] = str_replace([__STADIR__, '_' . $file . '.log', '/'], '', $model_files);
    }
    return $str;
}
function get_micro():string
{
    return sprintf("%.6f",microtime(true));
}


function get_ut():float
{
    return floatval(bcadd(get_micro(), "0", 6));
}

function get_microtime($length=1000):int
{
    return intval( bcmul(get_micro(), (string)$length, 0));
}

function ge_tum():int
{
    return function_exists('memory_get_usage') ? memory_get_usage() : 0;
}

/**
 * @return string
 * 消耗时间
 */
function ut(float $star_time):string
{
    return bcsub((string)get_ut(), (string)$star_time, 4) . 'S';
}

function ms_log(float $star_time):int
{
    return (int)bcmul(bcsub((string)get_ut(), (string)$star_time, 5),"1000",0);
}


function getum(): int
{
    //getrusage();
    //var_dump( memory_get_peak_usage(),' ',memory_get_usage());
    return function_exists('memory_get_usage') ? memory_get_usage() : 0;
}
function um(float $start_memory)
{
    return formatSize(intval(bcsub((string)getum(), (string)$start_memory, 0)), 4);
}

function xn_url_parse($request_url): array
{
    $request_url = ltrim(str_replace('/?', '/', $request_url), '/');
    $request = explode('?', $request_url);
    $r = explode('/', $request[0]);

    if (!empty($request[1])) {
        parse_str($request[1], $arr);
        $r += $arr;
    }
    return $r;
}

function parseName(string $name,int $type = 0,bool $uc_first = true): string
{
    if ($type) {
        $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
            return strtoupper($match[1]);
        }, $name);
        return $uc_first ? ucfirst($name) : lcfirst($name);
    } else {
        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
    }
}

function file_read($file):?string
{
    $fp = fopen($file, "r");
    if (!$fp) {
        return null;
    }
    $readData = '';
    $chunk = 4096;
    $size = filesize($file);
    $fs = sprintf("%u", $size);
    $max = (intval($fs) == PHP_INT_MAX) ? PHP_INT_MAX : $size;
    for ($len = 0; $len < $max; $len += $chunk) {
        $seekSize = ($max - $len > $chunk) ? $chunk : $max - $len;
        fseek($fp, ($len + $seekSize) * -1, SEEK_END);

        $readData = fread($fp, $seekSize) . $readData;
    }
    fclose($fp);
    return $readData;
}


function xn_safe_key($key = '')
{
    return !empty($key) ? $key : _CONF('auth_key');
}

function xn_urlencode(string $s): string
{
    $s = str_replace('-', '_2d', $s);
    $s = str_replace('.', '_2e', $s);
    $s = str_replace('+', '_2b', $s);
    $s = str_replace('=', '_3d', $s);
    $s = urlencode($s);
    return str_replace('%', '_', $s);
}

function xn_urldecode($s): string
{
    $s = str_replace('_', '%', $s);
    return urldecode($s);
}

function createLetterRange($length): array
{
    $range = [];
    $letters = range('A', 'Z');
    for ($i = 0; $i < $length; $i++) {
        $position = $i * 26;
        foreach ($letters as $letter) {
            $position++;
            if ($position <= $length) $range[] = ($position > 26 ? $range[$i - 1] : '') . $letter;
        }
    }
    return $range;
}

function getGuid(): string
{
    if (function_exists('com_create_guid') === true) {
        return trim(com_create_guid(), '{}');
    }
    mt_srand();
    return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535));
}

function ToInt($ip): float|int
{
    $iparr = explode('.', $ip);
    $num = 0;
    $max =  count($iparr);
    for ($i = 0; $i < $max; $i++) {
        $num += intval($iparr[$i]) * pow(256, count($iparr) - ($i + 1));
    }
    return $num;
}

function Xcopy($source, $destination, $child = 1): bool
{

    if (!is_dir($source)) {
        return false;
    }

    if (!is_dir($destination)) {
        mkdir($destination, 0777, true) && chmod($destination, 0777);
    }

    $handle = dir($source);
    while ($entry = $handle->read()) {
        if (($entry != ".") && ($entry != "..")) {
            if (is_dir($source . "/" . $entry)) {
                $child and Xcopy($source . "/" . $entry, $destination . "/" . $entry, $child);
            } else {
                copy($source . "/" . $entry, $destination . "/" . $entry);
            }
        }
    }
    return true;
}

/**
 * 获取文件地图
 */
function getFileMap(string $path,array $arr = []): array
{
    if (is_dir($path)) {
        $dir = scandir($path);
        foreach ($dir as $file_path) {
            if ($file_path != '.' && $file_path != '..') {
                $temp_path = $path . '/' . $file_path;
                $arr[$temp_path] = $file_path;
                if (is_dir($temp_path)) {
                    $arr = getFileMap($temp_path, $arr);
                }
            }
        }
    }
    return  $arr;
}


// 递归遍历目录
function glob_recursive(string $pattern,int $flags = 0): false|array
{
    $files = glob($pattern, $flags);
    foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
        $files = array_merge($files, glob_recursive($dir . '/' . basename($pattern), $flags));
    }
    return $files;
}


//
//// 递归删除目录，这个函数比较危险，传参一定要小心
function rmdir_recursive(string $dir, int $keeper = 0): bool
{
    if ($dir == '/' || $dir == './' || $dir == '../') return FALSE;// 不允许删除根目录，避免程序意外删除数据。
    if (!is_dir($dir)) return FALSE;
    !str_ends_with($dir, '/') and $dir .= '/';
    $files = glob($dir . '*'); // +glob($dir.'.*')
    foreach (glob($dir . '.*') as $v) {
        if (!str_ends_with($v, '.') && !str_ends_with($v, '..')) $files[] = $v;
    }
    $file_arr = $library = [];
    if ($files) {
        foreach ($files as $file) {
            if (is_dir($file)) {
                $library[] = $file;
            } else {
                $file_arr[] = $file;
            }
        }
    }
    if ($file_arr) {
        foreach ($file_arr as $file) {
            xn_unlink($file);
        }
    }
    if ($library) {
        foreach ($library as $file) {
            rmdir_recursive($file);
        }
    }
    if (!$keeper) xn_rmdir($dir);
    return TRUE;
}


function xn_copy(string $src,string $dest): bool
{
    return is_file($src) && copy($src, $dest);
}

function xn_rmdir(string $dir): bool
{
    return is_dir($dir) && rmdir($dir);
}

function xn_unlink(string $file): bool
{
    return is_file($file) && unlink($file);
}

function xn_filemtime(string $file): false|int
{
    return is_file($file) ? filemtime($file) : 0;
}


function get_uniqid(int $num,int $s = 0): string
{
    mt_srand();
    return substr(uniqid(md5(microtime(true) . mt_rand(10000, 90000))), $s, $num);
}

function xn_json_encode(mixed $data): false|string
{
    return json_encode($data, JSON_PRESERVE_ZERO_FRACTION | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

function xn_json_decode(string $json, $isArray = true): mixed
{
    return json_decode($json, $isArray);
}


function _CONF($key, $def = '')
{
    if (empty($key)) {
        return $_ENV['conf'];
    } else {
        return $_ENV['conf'][$key] ?? $def;
    }
}

function _SYS($key = '', $def = '')
{
    if (empty($key)) {
        return $_ENV['sys'];
    } else {
        return $_ENV['sys'][$key] ?? $def;
    }
}


function curl_get($url, $cookie = [], $timeout = 30, $ref = ''): bool|string
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    if (!empty($ref)) {
        curl_setopt($ch, CURLOPT_REFERER, $ref);
    }
    if ($cookie) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $cookie);
    }
    curl_setopt($ch, CURLOPT_FAILONERROR, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
}

function curl_post($url, $data, $user_agent = '', $cookie = '', $timeout = 30, $https = 0): bool|string
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
    if ($https == 1) {
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSLVERSION, 1);
    }
    if ($cookie) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, ["Cookie: $cookie"]);
    }
    if (!empty($user_agent)) {
        curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    }
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    $temp = curl_exec($ch);
    curl_close($ch);

    return $temp;
}


function https_get($url, $header = '', $user_agent = '', $timeout = 30): bool|string
{
    return https_post($url, '', $header, $user_agent, $timeout);
}

function https_post(string $url,string $post = '',string|array $header = '',string $user_agent = '',int $timeout = 30): bool|string
{

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    //curl_setopt($ch, CURLOPT_HEADER, 2); // 1/2
    curl_setopt($ch, CURLOPT_URL, $url);
    if (!empty($user_agent)) {
        curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    }

    if (str_replace('https://', '', $url) != $url) {

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

//		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
//		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在，默认可以省略
    }

    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

    if ($post) {
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    }
    if ($header) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    }

    (!ini_get('safe_mode') && !ini_get('open_basedir')) && curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转, 安全模式不允许

    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    $data = curl_exec($ch);
    //[$header, $data] = explode("\r\n\r\n", $data);
    //$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    //var_dump($http_code);
//	if ($http_code == 301 || $http_code == 302) {
//		$matches = [];
//		preg_match('/Location:(.*?)\n/', $header, $matches);
//		$url = trim(array_pop($matches));
//		curl_setopt($ch, CURLOPT_URL, $url);
//		curl_setopt($ch, CURLOPT_HEADER, false);
//		$data = curl_exec($ch);
//	}
    curl_close($ch);
    return $data;
}


function in_string($s, $str): bool
{
    if (!$s || !$str) return FALSE;
    $s = ",$s,";
    $str = ",$str,";
    return str_contains($str, $s);
}


function xn_encrypt(string $txt,string $key = ''): string
{
    empty($key) && $key = xn_safe_key();
    return encrypt($key, $txt);
    //return xn_urlencode(base64_encode(xxtea_encrypt($txt, $key)));
}

function xn_decrypt(string $txt,string $key = ''): string
{
    empty($key) && $key = xn_safe_key();
    return decrypt($key, $txt);
}



function str_sql_with($k): string
{
    $arr = explode('.', $k);
    if (!empty($arr[1])) {
        return $arr[0] . '.`' . $arr[1] . '`';
    } else {
        return '`' . $k . '`';
    }
}

function db_to_sqladd($name, $item, $type, &$val): string
{
    $s = '';
    if (is_numeric($name)) {
        foreach ($item as $key => $row) {
            $s .= db_to_sqladd($key, $row, $type, $val);
        }
    } else {
        $index = str_sql_with($name);
        empty($type) && $type = 'AND';
        if (!is_array($item)) {
            $val[] = $item;
            $s .= ' ' . $index . ' = ? '.$type;
        } else {
            $zk = key($item);
            if ($zk===0 && ($item[$zk] == "N" || $item[$zk] == "NULL")) {
                $s .= ' ' . $index . ' IS NULL  ' . $type . ' ';
            }else if ($zk===0 && ($item[$zk] == "NN" || $item[$zk] == "NNULL")) {
                $s .= ' ' . $index . ' IS NOT NULL ' . $type . ' ';
            }else if ($zk == "OR") {
                $s .= '(';
                $s .= db_to_sqladd($name, $item[$zk], 'OR', $val);
                $s = substr($s, 0, -4);
                $s .= ') AND ';
            }else if (is_numeric($zk) && is_array($item[$zk])) {
                foreach ($item as $row) {
                    $s .= db_to_sqladd($name, $row, $type, $val);
                }
            }else if (is_string($zk) && count($item)>1) {
                foreach ($item as $k=> $row) {
                    $s .= db_to_sqladd($name, [$k=> $row], $type, $val);
                }
            } else {
                $zk=='=>' && $zk='>=';
                $zk=='=<' && $zk='<=';
                //var_dump($zk);
                switch ($zk) {
                    case '>':
                    case '>=':
                    case '<':
                    case '<=':
                    case '!=':
                    case '=':
                        $s .= ' ' . $index . ' ' . $zk . ' ? ' . $type . ' ';
                        $val[] = $item[$zk];
                        break;
                    case '><':
                        $s .= ' ' . $index . ' BETWEEN ? AND ? ' . $type . ' ';
                        $val[] = $item[$zk][0];
                        $val[] = $item[$zk][1];
                        break;
                    case 'MA':
                    case 'MATCH':
                        $s .= ' MATCH(' . $index . ') AGAINST(?) ' . $type . ' ';
                        $val[] = $item[$zk];
                        break;
                    case 'MAI':
                    case 'MATCHI':
                        $s .= ' MATCH(' . $index . ') AGAINST(? IN BOOLEAN MODE) ' . $type . ' ';
                        $val[] = $item[$zk];
                        break;
                        /*
                    case "FI":
                    case "FIND_IN_SET":
                        $s .= ' FIND_IN_SET( ? , ' . $index . ') ' . $type . ' ';
                        $val[] = implode(',', $item[$zk]);
                        break;*/
                    case "LL":
                    case "LLIKE":
                        $s .= ' ' . $index . ' LIKE ? ' . $type . ' ';
                        $val[] = "%" . $item[$zk];
                        break;
                    case "RL":
                    case "RLIKE":
                        $s .= ' ' . $index . ' LIKE ? ' . $type . ' ';
                        $val[] = $item[$zk] . "%";
                        break;
                    case "LI":
                    case "LIKE":
                        $s .= ' ' . $index . ' LIKE ? ' . $type . ' ';
                        $val[] = "%" . $item[$zk] . "%";
                        break;
                    case "NL":
                    case "NLIKE":
                        $s .= ' ' . $index . ' NOT LIKE ? ' . $type . ' ';
                        $val[] = "%" . $item[$zk] . "%";
                        break;
                    default:

                            $item = filter_unique($item);
                            empty($item) && $item=[0];
                            $prepare = rtrim(str_pad('?', 2 * count($item), ',?'), ',');
                            $s .= ' ' . $index . ' IN (' . $prepare . ') ' . $type . ' ';
                            array_push($val, ...$item);

                }
            }
        }
    }
    return $s;
}

/**
['name'=>123] =>  `name` = ?
['name'=>[123,456]] =>  `name` IN (?,?)
* 支持Key >   >=   <   <=   !=   =
['name'=>['>'=>123]] => WHERE  `name` > ?
 * NULL    is null
 * NNULL   is not null
 * MA || MATCH     ->  MATCH( name ) AGAINST(?)    全文索引
 * MAI || MATCHI   ->  MATCH( name ) AGAINST(? IN BOOLEAN MODE) 全文索引
----错误，取消 * FI || FIND_IN_SET    -> FIND_IN_SET(?, name)  查找
 * LLIKE  -> LIKE %?   左模糊
 * RLIKE  -> LIKE ?%   右模糊
 * LIKE   -> LIKE %?%  模糊
 * NLIKE  -> NOT LIKE %?%  模糊
['name'=>['N']] || ['name'=>['NULL']] => WHERE  `name` IS NULL
['name'=>['NN']] || ['name'=>['NNULL']] => WHERE  `name` IS NOT NULL
['name'=>['MA'=>'abc']] => WHERE  MATCH( `name` ) AGAINST(?)  => WHERE  MATCH( `name` ) AGAINST('abc')
['name'=>['MAI'=>'abc']] => WHERE  MATCH( `name` ) AGAINST(? IN BOOLEAN MODE)  => WHERE  MATCH( `name` ) AGAINST('abc' IN BOOLEAN MODE)
----错误，取消 ['name'=>['FI'=>'abc']] => WHERE  FIND_IN_SET(?, `name`)  => WHERE  FIND_IN_SET('abc', `name`)
['name'=>['LL'=>'abc']] || ['name'=>['LLIKE'=>'abc']] => WHERE  `name` LIKE %?  => WHERE  `name` LIKE %abc
['name'=>['RL'=>'abc']] ||['name'=>['RLIKE'=>'abc']] => WHERE  `name` LIKE ?%  => WHERE  `name` LIKE abc%
['name'=>['LI'=>'abc']] ||['name'=>['LIKE'=>'abc']] => WHERE  `name` LIKE %?%  => WHERE  `name` LIKE %abc%
['name'=>['NL'=>'abc']] || ['name'=>['NLIKE'=>'abc']] => WHERE  `name` NOT LIKE %?%  => WHERE  `name` NOT LIKE %abc%
['name'=>['><'=>[345,1234]]] => WHERE  `name` BETWEEN ? AND ?
高级
['OR'=>['name'=>['N'],'age'=>['NN']]]   WHERE ( `name` IS NULL  OR  `age` IS NOT NULL)
['OR'=>['name'=>[1,2,3],'age'=>[4,5]]]   WHERE ( `name` IN (?,?,?)  OR  `age` IN (?,?))
['OR'=>['name'=>[1,2,3],'age'=>[4,5]],'id'=>[1,2,3]] WHERE ( `name` IN (?,?,?)  OR  `age` IN (?,?)) AND `id` IN (?,?,?)
['name'=>['OR'=>['>='=>100,'<='=>300]],'age'=>['>'=>20]] WHERE ( `name` >= ?  OR  `name` <= ?) AND `age` > ?
 */
function db_cond_to_sqladd(array $cond): array
{
    $s = '';
    $val = [];
    if (!empty($cond)) {
        $s = ' WHERE ';
        foreach ($cond as $name => $v) {
            if($name=="OR"){
                $s.='(';
                foreach ($v as $k=> $row){
                    $s.='(';
                    $s .= db_to_sqladd($k, $row, 'AND', $val);
                    $s = substr($s, 0, -4);
                    $s.=') OR ';
                }
                $s = substr($s, 0, -4);
                $s.=') OR ';
            }else{
                $s .= db_to_sqladd($name, $v, 'AND', $val);
            }
        }
        $s = substr($s, 0, -4);
    }
    if(!empty($cond) && !is_array($cond)){
        var_dump("db_cond_to_sqladd");
        var_dump($cond);
    }
    return ['sql' => trim($s), 'val' => $val];
}


function db_orderby_to_sqladd(array $orderby): array
{
    $s = '';
    $val=[];
    if (!empty($orderby)) {
        $s .= ' ORDER BY ';
        $comma = '';
        foreach ($orderby as $k => $v) {
            $_k = str_sql_with($k);
            if(is_array($v)){
                $item = filter_unique($v);
                empty($item) && $item=[0];
                $prepare = rtrim(str_pad('?', 2 * count($item), ',?'), ',');
                array_push($val, ...$item);
                $s .= $comma . "FIELD($_k,$prepare)";
            }else{
                $s .= $comma . "$_k " . ($v == 1 ? ' ASC ' : ' DESC ');
            }
            $comma = ',';
        }
    }
    return ['sql'=>$s,'val'=>$val];
}


function db_array_to_update_sqladd(array $arr): array
{
    $s = '';
    $val = [];
    foreach ($arr as $k => $v) {
        $op = substr($k, -1);
        if ($op == '=') {
            $k = substr($k, 0, -1);
            $_k = str_sql_with($k);
            $s .= "$_k=?,";
            $val[] = $v;
        } else if ($op == '+' || $op == '-'||$op == '*' || $op == '/') {
            $k = substr($k, 0, -1);
            $_k = str_sql_with($k);
            $s .= "$_k=$_k$op?,";
            $val[] = $v;
        } else {
            $_k = str_sql_with($k);
            $s .= "$_k=?,";
            $val[] = $v;
        }
    }
    return ['sql' => substr($s, 0, -1), 'val' => $val];
}

/*
    $arr = array(
        'name'=>'abc',
        'date'=>12345678900,
    )
    db_array_to_insert_sqladd($arr);
*/
function db_array_to_insert_sqladd(array $arr): array
{
    $keys = [];
    $values = [];
    foreach ($arr as $k => $v) {
        $keys[] = $k;
        $values[] = $v;
    }
    $keystr = implode(',', $keys);
    $prepare = rtrim(str_pad('?', 2 * count($values), ',?'), ',');
    $sqladd = "($keystr) VALUES ($prepare)";
    return ['sql' => $sqladd, 'val' => $values];
}




/**
 * 获取方法节点列表
 *
 * @return array
 * @throws \ReflectionException
 */
function getMethodList()
{

    eachController(function (ReflectionClass $reflection, $prenode) use (&$nodes) {
        $empty = array(" ", "　", "\t", "\n", "\r");
        $class_name = $reflection->getName();
        $exp = explode('\\', strtolower($class_name));
        if($exp[0]=='app'){
            array_shift($exp);
            $class_name=implode('\\',$exp);
        }
        if (end($exp) == 'index') {
            unset($exp[count($exp) - 1]);
        }
       // var_dump($class_name);
        $_class_name = implode('/', $exp);
        $class_comment = $reflection->getDocComment();
        //$namespace = strtolower($reflection->getNamespaceName());
        $plugin = $module = '';
        preg_match('|@plugin(.+)|i', $class_comment, $plugins);
        preg_match('|@title(.+)|i', $class_comment, $title);
        preg_match('|@rank(.+)|i', $class_comment, $rank);
        preg_match('|@icon(.+)|i', $class_comment, $icon);
        preg_match('|@component(.+)|i', $class_comment, $component);
        $class_comment = str_replace($empty, '', $class_comment);
        !empty($plugins[1]) && $plugin = strtolower(trim($plugins[1]));
        $module = $exp[0];
        unset($exp[count($exp) - 1]);
        $parent_node = implode('/', $exp);
        $nodes[$_class_name] = [
            'class' => true,
            'class_name' => $class_name,
            'namespace' => ($parent_node == $module && stripos($class_comment, '@menutrue') !== false) ? '' : $parent_node,
            'plugin' => $plugin,
            'title' => isset($title[1]) ? trim($title[1]) : $_class_name,
            'component' => isset($component[1]) ? trim($component[1]) : '',
            'icon' => isset($icon[1]) ? trim($icon[1]) : 'el-icon-menu',
            'menu' => stripos($class_comment, '@menutrue') !== false,
            'auth' => stripos($class_comment, '@authtrue') !== false,
            'login' => stripos($class_comment, '@logintrue') !== false,
            'hidden' => stripos($class_comment, '@hiddentrue') !== false,
            'active' => stripos($class_comment, '@activetrue') !== false,
            'rank' => isset($rank[1]) ? intval($rank[1]) : 99,
        ];

        foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            //continue;

            //var_dump($method->class);
            $m_class_name = $method->class;
            $exp = explode('\\', strtolower($m_class_name));
            if($exp[0]=='app'){
                array_shift($exp);
            }
            $m_class_name=implode('\\',$exp);
            if ($class_name != $m_class_name) {
                continue;
            }
            $action = preg_replace('/^action_(.*)/', '$1', strtolower($method->getName()));
            if ($action == '__construct' || strtolower($method->getName()) == $action) {
                continue;
            }
            $node = $prenode . $action;
            $comment = $method->getDocComment();
            preg_match('|@rank(.+)|i', $comment, $ranks);
            preg_match('|@title(.+)|i', $comment, $title);
            preg_match('|@component(.+)|i', $comment, $component);
            preg_match('|@icon(.+)|i', $comment, $icon);
            $comment = str_replace($empty, '', $comment);
            $nodes[$node] = [
                'title' => isset($title[1]) ? trim($title[1]) : '',
                'component' => isset($component[1]) ? trim($component[1]) : '',
                'icon' => isset($icon[1]) ? trim($icon[1]) : 'el-icon-menu',
                'class_name' => $class_name,
                'namespace' => $_class_name,
                'plugin' => $plugin,
                'action' => $action,
                'menu' => stripos($comment, '@menutrue') !== false,
                'auth' => stripos($comment, '@authtrue') !== false ? true : $nodes[$_class_name]['auth'],
                'login' => stripos($comment, '@logintrue') !== false ? true : $nodes[$_class_name]['login'],
                'hidden' => stripos($comment, '@hiddentrue') !== false ? true : $nodes[$_class_name]['hidden'],
                'active' => stripos($comment, '@activetrue') !== false ? true : $nodes[$_class_name]['active'],
                'rank' => isset($ranks[1]) ? intval($ranks[1]) : 99,
            ];
            if (stripos($nodes[$node]['title'], '@') !== false) $nodes[$node]['title'] = '';
        }
    });
    return $nodes;
}

/**
 * 控制器扫描回调
 */
function eachController(callable $callable): void
{
    $_files = [];

    foreach ((array)$_ENV['plugin_controllers_files'] as $class => $file) {
        $name = str_replace('\\', '/', $class);
        $_files[$class] = 1;
        if (is_file($file)) {
            if (class_exists('\\App\\' . $class)) {
                call_user_func($callable, new ReflectionClass('\\App\\' . $class), parseString($name . '/'));
            }
        }
    }

    foreach (scanPath(__APPDIR__ . "Controller/*/*") as $file) {
        $name = str_replace([__APPDIR__ . "Controller/", '.php'], '', $file);
        $class =  str_replace('/', '\\', $name);
        if (empty($_files[$class]) && is_file($file)) {
            if (class_exists('\\App\\' .$class)) {
                call_user_func($callable, new ReflectionClass('\\App\\' .$class), parseString($name . '/'));
            }
        }
    }

}

/**
 * 获取方法节点列表
 *
 * @return array
 * @throws \ReflectionException
 */
function getTaskMethodList(): array
{
    $nodes=[];
    eachTaskController(function (ReflectionClass $reflection, $prenode) use (&$nodes) {
        $class_name = $reflection->getName();
        $exp = explode('\\', strtolower($class_name));
        if (end($exp) == 'index') {
            unset($exp[count($exp) - 1]);
            $_class_name = implode('/', $exp);
        } else {
            $_class_name = str_replace('\\', '/', strtolower($class_name));
        }

        $nodes[$_class_name] = ['class' => true, 'class_name' => $class_name];

        foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            if ($class_name != $method->class) {
                continue;
            }
            $action = strtolower($method->getName());
            if ($action == '__construct') {
                continue;
            }
            $node = $prenode . $action;

            $nodes[$node] = [
                'action' => $action,
                'class_name' => $class_name,
                'namespace' => $_class_name,
            ];
        }
    });

    return $nodes;
}

/**
 * 控制器扫描回调
 */
function eachTaskController(callable $callable): void
{
    $_files = [];

    foreach ((array)$_ENV['plugin_task_controllers_files'] as $class => $file) {

        $name = str_replace('\\', '/', $class);
        $_files[$class] = 1;
        if (is_file($file)) {
            if (class_exists($class)) {
                call_user_func($callable, new ReflectionClass($class), parseString($name . '/'));
            }
        }

    }


    foreach (scanPath(__APPDIR__ . "Task/*/*") as $file) {
        $name = str_replace([__APPDIR__ . "Task/", '.php'], '', $file);
        $class = '\\Task\\' . str_replace('/', '\\', $name);
        if (empty($_files[$class]) && is_file($file)) {
            if (class_exists($class)) {
                call_user_func($callable, new ReflectionClass($class), parseString($name . '/'));
            }
        }
    }

}

function aes_encrypt($data, $key) {
    $iv = xn_rand(16);
    $key = mb_strcut($key,-16);
    $encrypted = openssl_encrypt($data, 'aes-256-cbc', $iv.$key, OPENSSL_RAW_DATA, $iv);
    return $iv . base64_encode($encrypted);
}